using System;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;

using Impact.Infrastructure.Exceptions;
using Impact.Model.Objects;

namespace Impact.Web.Foundation.Handlers
{
    /// <summary>
    /// Recieves pingbacks from other blogs and websites, and 
    /// registers them as a comment.
    /// Borrowed from BlogEngine.NET
    /// </summary>
    public class PingbackHandler : IHttpHandler
    {

        /// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler that 
        /// implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext"></see> 
        /// object that provides references to the intrinsic server objects 
        /// (for example, Request, Response, Session, and Server) used to service HTTP requests.
        /// </param>
        public void ProcessRequest( HttpContext context )
        {
            try
            {
                string xml = ParseRequest( context );
                if ( !xml.Contains( "<methodName>pingback.ping</methodName>" ) )
                    return;

                XmlDocument doc = new XmlDocument();
                doc.LoadXml( xml );

                XmlNodeList list = doc.SelectNodes( "methodCall/params/param/value/string" );
                string sourceUrl = list[0].InnerText.Trim();
                string targetUrl = list[1].InnerText.Trim();

                string title = string.Empty;
                bool hasLink = ExamineSourcePage( sourceUrl, targetUrl, out title );
                if ( hasLink )
                {

                    Article article = Article.FetchByUrl( new Uri( targetUrl, UriKind.Absolute ) );
                    if ( Article.IsValid( article ) && IsFirstPingBack( article, sourceUrl ) )
                    {
                        AddComment( sourceUrl, article, title );
                        context.Response.Write( "OK" );
                    }
                }
            }
            catch ( Exception ex )
            {
                ExceptionManager.Handle( string.Format( "An error occurred processing pingback: {0}", ex.ToString() ) );
                context.Response.Write( "ERROR" );
            }
        }

        /// <summary>
        /// Insert the pingback as a comment on the post.
        /// </summary>
        private void AddComment( string sourceUrl, Article article, string title )
        {
            ArticleComment comment = new ArticleComment();
            comment.Author = GetDomain( sourceUrl );
            comment.AuthorUrl = sourceUrl;
            comment.Body = "Pingback from " + comment.Author + Environment.NewLine + Environment.NewLine + title;
            comment.CreatedOn = DateTime.Now;
            comment.ArticleId = article.ArticleId;
            comment.Save();
        }

        /// <summary>
        /// Retrieves the content of the input stream
        /// and return it as plain text.
        /// </summary>
        private static string ParseRequest( HttpContext context )
        {
            byte[] buffer = new byte[context.Request.InputStream.Length];
            context.Request.InputStream.Read( buffer, 0, buffer.Length );
            return System.Text.Encoding.Default.GetString( buffer );
        }

        /// <summary>
        /// Parse the source URL to get the domain.
        /// It is used to fill the Author property of the comment.
        /// </summary>
        private static string GetDomain( string sourceUrl )
        {
            int start = sourceUrl.IndexOf( "://" ) + 3;
            int stop = sourceUrl.IndexOf( "/", start );
            return sourceUrl.Substring( start, stop - start ).Replace( "www.", string.Empty );
        }

        /// <summary>
        /// Checks to see if the source has already pinged the target.
        /// If it has, there is no reason to add it again.
        /// </summary>
        private bool IsFirstPingBack( Article article, string sourceUrl )
        {
            return article.ArticleComments.GetList().Find( delegate( ArticleComment comment )
            {
                return !string.IsNullOrEmpty( comment.AuthorUrl )
                    && comment.AuthorUrl.Equals( sourceUrl, StringComparison.OrdinalIgnoreCase );
            } ) == null;
        }

        /// <summary>
        /// Parse the HTML of the source page.
        /// </summary>
        /// <returns>true if the sourceUrl has a link to the post</returns>
        private bool ExamineSourcePage( string sourceUrl, string targetUrl, out string title )
        {
            bool hasLink = false;
            title = string.Empty;
            using ( WebClient client = new WebClient() )
            {
                string html = client.DownloadString( sourceUrl );
                Match m = Regex.Match(html, @"(?<=<title.*>)([\s\S]*)(?=</title>)", RegexOptions.IgnoreCase);
                if ( m != null ) title = m.Value.Trim();
                hasLink = html.ToLowerInvariant().Contains( targetUrl.ToLowerInvariant() );
            }
            return hasLink;
        }

        /// <summary>
        /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"></see> instance.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>
        public bool IsReusable
        {
            get { return true; }
        }

    }
}
